using System;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace AvalonDock
{
    /// <summary>
    /// Anchor types
    /// </summary>
    public enum AnchorStyle
    {
        /// <summary>
        /// No anchor style, while content is hosted in a <see cref="DocumentPane"/> or a <see cref="FloatingWindow"/>
        /// </summary>
        None,

        /// <summary>
        /// Top border anchor
        /// </summary>
        Top,

        /// <summary>
        /// Left border anchor
        /// </summary>
        Left,

        /// <summary>
        /// Bottom border anchor
        /// </summary>
        Bottom,

        /// <summary>
        /// Right border anchor
        /// </summary>
        Right
    }

    /// <summary>
    /// Defines a pane that can contain contents of type <see cref="DockableContent"/>
    /// </summary>
    /// <remarks>Usually a <see cref="DockablePane"/> is used to arrange a series of <see cref="DockableContent"/> in TabControl like model.
    /// A DockablePane can be redocked to a border of the parent <see cref="DockingManager"/>, can be floated in an external window and can be autohidden.
    /// When docked into a docking manager the <see cref="DockablePane.Anchor"/> property gives the border to which it's docked.
    /// See <see cref="DockablePaneCommands"/> to get commands that are supported by DockablePane objects.</remarks>
    /// <seealso cref="DockableContent"/>
    /// <seealso cref="DockingManager"/>
    public class DockablePane : Pane
    {
        #region Constants and Fields

        public static readonly DependencyPropertyKey AnchorPropertyKey =
            DependencyProperty.RegisterAttachedReadOnly(
                "Anchor", typeof(AnchorStyle), typeof(DockablePane), new UIPropertyMetadata(AnchorStyle.None));

        public static readonly DependencyProperty ShowTabsProperty = DependencyProperty.Register(
            "ShowTabs", typeof(bool), typeof(DockablePane), new UIPropertyMetadata(true));

        /// <summary>
        /// This guid is saved with the dockable content so that can be restored in the case is 
        /// referenced by a dockable content
        /// </summary>
        internal Guid ID = Guid.NewGuid();

        /// <summary>
        /// CanAutohide Read-Only Dependency Property
        /// </summary>
        private static readonly DependencyPropertyKey CanAutohidePropertyKey =
            DependencyProperty.RegisterReadOnly(
                "CanAutohide", typeof(bool), typeof(DockablePane), new FrameworkPropertyMetadata(false));

        public static readonly DependencyProperty CanAutohideProperty = CanAutohidePropertyKey.DependencyProperty;

        private bool isMouseDown;

        private Point ptStartDrag;

        #endregion

        #region Constructors and Destructors

        static DockablePane()
        {
            //This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
            //This style is defined in themes\generic.xaml
            DefaultStyleKeyProperty.OverrideMetadata(
                typeof(DockablePane), new FrameworkPropertyMetadata(typeof(DockablePane)));
        }

        public DockablePane()
        {
            Loaded += DockablePane_Loaded;
            Unloaded += DockablePane_Unloaded;

            KeyboardNavigation.SetTabNavigation(this, KeyboardNavigationMode.Cycle);
        }

        #endregion

        #region Public Properties

        public AnchorStyle Anchor
        {
            get
            {
                return (AnchorStyle)GetValue(AnchorPropertyKey.DependencyProperty);
            }
            internal set
            {
                SetValue(AnchorPropertyKey, value);
            }
        }

        // Using a DependencyProperty as the backing store for Anchor.  This enables animation, styling, binding, etc...

        /// <summary>
        /// Gets the CanAutohide property.  This dependency property 
        /// indicates if contents inside pane can be autohidden.
        /// </summary>
        public bool CanAutohide
        {
            get
            {
                return (bool)GetValue(CanAutohideProperty);
            }
        }

        public bool IsAutoHidden
        {
            get
            {
                return Items.Cast<DockableContent>().FirstOrDefault(c => c.State == DockableContentState.AutoHide)
                       != null;
            }
        }

        public override bool IsDocked
        {
            get
            {
                return IsSurfaceVisible;
            }
        }

        public bool ShowTabs
        {
            get
            {
                return (bool)GetValue(ShowTabsProperty);
            }
            set
            {
                SetValue(ShowTabsProperty, value);
            }
        }

        #endregion

        #region Properties

        protected override bool IsSurfaceVisible
        {
            get
            {
                foreach (ManagedContent managedContent in Items)
                {
                    if (managedContent is DocumentContent)
                    {
                        continue;
                    }

                    if (((DockableContent)managedContent).State == DockableContentState.Docked
                        || ((DockableContent)managedContent).State == DockableContentState.None)
                    {
                        return true;
                    }
                }

                return false;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Close pane and all contained contents
        /// </summary>
        /// <returns>True if all content has been closed, false if at least one content couldn't be closed.</returns>
        /// <remarks></remarks>
        public virtual bool Close()
        {
            DockableContent[] cntsToClose = Items.OfType<DockableContent>().ToArray();
            bool res = true;
            foreach (DockableContent cnt in cntsToClose)
            {
                if (!cnt.Close())
                {
                    res = false;
                }
            }

            return res;
        }

        public DockableStyle GetCumulativeDockableStyle()
        {
            DockableStyle style = DockableStyle.Dockable;

            if (Items.Count == 1 && Items[0] is DocumentContent)
            {
                style = DockableStyle.Document;
            }
            else
            {
                style = Items.Cast<DockableContent>().Aggregate(style, (current, content) => current & content.DockableStyle);
            }

            return style;
        }

        /// <summary>
        /// Close pane and hide all contained contents
        /// </summary>
        /// <returns>True if all content has been hidden, false if at least one content couldn't be hidden.</returns>
        /// <remarks></remarks>
        public virtual bool Hide()
        {
            DockableContent[] cntsToClose = Items.OfType<DockableContent>().ToArray();
            bool res = true;
            foreach (DockableContent cnt in cntsToClose)
            {
                if (!cnt.Hide())
                {
                    res = false;
                }
            }

            return res;
        }

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (_partHeader != null)
            {
                _partHeader.MouseDown += OnHeaderMouseDown;
                _partHeader.MouseMove += OnHeaderMouseMove;
                _partHeader.MouseUp += OnHeaderMouseUp;
                _partHeader.MouseEnter += OnHeaderMouseEnter;
                _partHeader.MouseLeave += OnHeaderMouseLeave;

                _partHeader.MouseRightButtonDown += (s, e) =>
                    {
                        if (_partHeader.ContextMenu == null)
                        {
                            FocusContent();
                            if (_partHeader.ContextMenu == null)
                            {
                                Dispatcher.BeginInvoke(
                                    DispatcherPriority.Input, new ThreadStart(() => OpenOptionsMenu(null)));

                                e.Handled = true;
                            }
                        }
                    };
            }

            var optionsMenuPopupElement = GetTemplateChild("PART_ShowContextMenuButton") as Border;

            if (optionsMenuPopupElement != null)
            {
                optionsMenuPopupElement.MouseLeftButtonDown += (s, e) =>
                    {
                        OpenOptionsMenu(s as UIElement);
                        e.Handled = true;
                    };
            }
        }

        public override bool OpenOptionsMenu(UIElement menuTarget)
        {
            if (cxOptions == null)
            {
                cxOptions =
                    TryFindResource(new ComponentResourceKey(typeof(DockingManager), ContextMenuElement.DockablePane))
                    as ContextMenu;
            }

            return base.OpenOptionsMenu(menuTarget);
        }

        /// <summary>
        /// Toggle auto hide state to all content inside the pane
        /// </summary>
        public virtual void ToggleAutoHide()
        {
            bool flag =
                Items.OfType<DockableContent>().FirstOrDefault(c => (c.DockableStyle & DockableStyle.AutoHide) == 0)
                == null;

            if (flag && GetManager() != null)
            {
                GetManager().ToggleAutoHide(this);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Closes or hides current content depending on HideOnClose property
        /// </summary>
        internal void CloseOrHideCurrentContent()
        {
            ((DockableContent)SelectedItem).CloseOrHide(false);
        }

        internal override ManagedContent RemoveContent(int index)
        {
            ManagedContent content = base.RemoveContent(index);
            var dockableContent = content as DockableContent;

            if (((dockableContent == null) || dockableContent.SavedStateAndPosition == null
                 || dockableContent.SavedStateAndPosition.ContainerPane != this)
                && Items.Count == 0)
            {
                var containerPanel = Parent as ResizingPanel;
                if (containerPanel != null)
                {
                    containerPanel.RemoveChild(this);
                }
            }

            return content;
        }

        internal void UpdateCanAutohideProperty()
        {
            SetCanAutohide(
                Items.Cast<DockableContent>().All(
                    c =>
                        {
                            bool flag = c.State == DockableContentState.Docked
                                        || c.State == DockableContentState.Document
                                        || c.State == DockableContentState.AutoHide;

                            flag = flag && ((c.DockableStyle & DockableStyle.AutoHide) > 0);
#if DEBUG
                            Debug.WriteLine(string.Format("{0} CanAutohide()= {1}", c.Title, flag));
#endif
                            return flag;
                        }));
        }

        /// <summary>
        /// Retrive a value indicating if the command can be executed based to the dockable content state
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        protected virtual bool CanExecuteCommand(ICommand command)
        {
            return true;
        }

        protected override void CheckItems(IList newItems)
        {
            if (newItems.Cast<object>().Any(newItem => !(newItem is DockableContent)))
            {
                throw new InvalidOperationException("DockablePane can contain only DockableContents!");
            }
        }

        protected override void OnCanExecuteCommand(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        protected override void OnExecuteCommand(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Command == DockablePaneCommands.Close)
            {
                Close();
                e.Handled = true;
            }
            else if (e.Command == DockablePaneCommands.Hide)
            {
                Hide();
                e.Handled = true;
            }
            else if (e.Command == DockablePaneCommands.ToggleAutoHide)
            {
                ToggleAutoHide();
                e.Handled = true;
            }
            //else if (e.Command == ShowOptionsCommand)
            //{
            //    OpenOptionsContextMenu();
            //    e.Handled = true;
            //}
        }

        //ContextMenu cxOptions = null;

        ///// <summary>
        ///// Open the option context menu
        ///// </summary>
        ///// <param name="menuTarget">Target element under which context menu will be shown. Pass null if context menu
        ///// should be shown at mouse position.</param>
        ///// <returns>True if context menu resource was found and open, false otherwise.</returns>
        //public bool OpenOptionsMenu(UIElement menuTarget)
        //{
        //    if (cxOptions == null)
        //    {
        //        cxOptions = TryFindResource(new ComponentResourceKey(typeof(DockingManager), ContextMenuElement.DockablePane)) as ContextMenu;
        //        if (cxOptions != null)
        //        {
        //            cxOptions.Opened += (s, e) => UpdateIsOptionsMenuOpen();
        //            cxOptions.Closed += (s, e) => UpdateIsOptionsMenuOpen();
        //        }
        //    }

        //    if (cxOptions != null)
        //    {
        //        cxOptions.DataContext = this.SelectedItem as DockableContent;

        //        foreach (MenuItem menuItem in cxOptions.Items.OfType<MenuItem>())
        //            menuItem.CommandTarget = this.SelectedItem as DockableContent;

        //        if (menuTarget != null)
        //        {
        //            cxOptions.Placement = PlacementMode.Bottom;
        //            cxOptions.PlacementTarget = menuTarget;
        //        }
        //        else
        //        {
        //            cxOptions.Placement = PlacementMode.MousePoint;
        //            cxOptions.PlacementTarget = this;
        //        }

        //        FocusContent();
        //        cxOptions.IsOpen = true;
        //    }

        //    return (cxOptions != null && cxOptions.IsOpen);
        //}

        ///// <summary>
        ///// Close the options context menu
        ///// </summary>
        //public void CloseOptionsMenu()
        //{
        //    if (cxOptions != null)
        //    {
        //        cxOptions.IsOpen = false;
        //        cxOptions = null;
        //    }
        //}

        ///// <summary>
        ///// Gets a value indicating if the options context menu is open
        ///// </summary>
        //public bool IsOptionsMenuOpen
        //{
        //    get { return (bool)GetValue(IsOptionsMenuOpenProperty); }
        //    protected set { SetValue(IsOptionsMenuOpenPropertyKey, value); }
        //}

        //// Using a DependencyProperty as the backing store for IsOptionsMenuOpen.  This enables animation, styling, binding, etc...
        //static readonly DependencyPropertyKey IsOptionsMenuOpenPropertyKey =
        //    DependencyProperty.RegisterReadOnly("IsOptionsMenuOpen", typeof(bool), typeof(DockablePane), new UIPropertyMetadata(false));

        //public static readonly DependencyProperty IsOptionsMenuOpenProperty = IsOptionsMenuOpenPropertyKey.DependencyProperty;

        //void UpdateIsOptionsMenuOpen()
        //{
        //    if (cxOptions != null)
        //    {
        //        var selectedContent = cxOptions.DataContext as DockableContent;

        //        if (selectedContent != null)
        //        {
        //            (selectedContent.ContainerPane as DockablePane).IsOptionsMenuOpen =
        //                cxOptions.IsOpen;
        //        }
        //    }            
        //}

        protected virtual void OnHeaderMouseDown(object sender, MouseButtonEventArgs e)
        {
            if (!e.Handled && SelectedItem != null)
            {
                FocusContent();

                if (((DockableContent)SelectedItem).State != DockableContentState.AutoHide)
                {
                    //ptStartDrag = e.MouseDevice.GetPosition(this);
                    ptStartDrag = e.GetPosition((IInputElement)VisualTreeHelper.GetParent(this));

                    isMouseDown = true;
                }
            }
        }

        protected virtual void OnHeaderMouseEnter(object sender, MouseEventArgs e)
        {
            isMouseDown = false;
        }

        protected virtual void OnHeaderMouseLeave(object sender, MouseEventArgs e)
        {
            isMouseDown = false;
        }

        protected virtual void OnHeaderMouseMove(object sender, MouseEventArgs e)
        {
            //Point ptMouseMove = e.GetPosition(this);
            Point ptMouseMove = e.GetPosition((IInputElement)VisualTreeHelper.GetParent(this));

            if (!e.Handled && isMouseDown && e.LeftButton == MouseButtonState.Pressed)
            {
                if (_partHeader != null && _partHeader.IsMouseOver)
                {
                    DockingManager manager = GetManager();
                    if (!manager.DragPaneServices.IsDragging && !IsMouseCaptured)
                    {
                        if (Math.Abs(ptMouseMove.X - ptStartDrag.X) > SystemParameters.MinimumHorizontalDragDistance
                            || Math.Abs(ptMouseMove.Y - ptStartDrag.Y) > SystemParameters.MinimumVerticalDragDistance)
                        {
                            isMouseDown = false;
                            ReleaseMouseCapture();

                            manager.Drag(this, this.PointToScreenDPI(e.GetPosition(this)), e.GetPosition(this));
                            e.Handled = true;
                        }
                    }
                }
            }
        }

        protected virtual void OnHeaderMouseUp(object sender, MouseButtonEventArgs e)
        {
            isMouseDown = false;
            ReleaseMouseCapture();
        }

        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);

            CommandBindings.Add(
                new CommandBinding(DockablePaneCommands.ToggleAutoHide, OnExecuteCommand, OnCanExecuteCommand));
            CommandBindings.Add(new CommandBinding(DockablePaneCommands.Close, OnExecuteCommand, OnCanExecuteCommand));
        }

        protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
        {
            Items.Cast<DockableContent>().ForEach(
                c =>
                    {
                        if (c.State == DockableContentState.None)
                        {
                            c.SetStateToDock();
                        }
                    });

            UpdateCanAutohideProperty();
            base.OnItemsChanged(e);
        }

        /// <summary>
        /// Provides a secure method for setting the CanAutohide property.  
        /// This dependency property indicates if contents inside pane can be autohidden.
        /// </summary>
        /// <param name="value">The new value for the property.</param>
        protected void SetCanAutohide(bool value)
        {
            SetValue(CanAutohidePropertyKey, value);
        }

        private void DockablePane_Loaded(object sender, RoutedEventArgs e)
        {
        }

        private void DockablePane_Unloaded(object sender, RoutedEventArgs e)
        {
            CloseOptionsMenu();
        }

        #endregion
    }
}